├── README.md ├── accept.php ├── admin.php ├── data.json ├── filter.txt ├── index.js ├── index.php ├── mystyle.css ├── screen.png ├── showJs.php └── wrtbwmon /README.md: -------------------------------------------------------------------------------- 1 | ## 路由流量监控管理平台 2 | 3 | ![软件截图](http://github.com/ibird/wrbwmonForOpenMS/raw/master/screen.png) 4 | ### What 5 | 路由流量监控管理平台是为了方便网络管理人员监控流量而做的一套基于openwrt的软件。 6 | 由后端shell脚本和前端web系统两部分组成。 7 | 后端shell脚本起到监控流量、同步mac白名单的作用。 8 | 前端web起到方便网络管理人员管理用户mac设备及用户流量的作用。 9 | 10 | ### Why 11 | 在网络流量有限,而连接用户未知的情况下,总是会经常出现流量超用滥用的情况。 12 | 在西电(当然诸如此类的网络环境应该还有很多),大多实验室、宿舍为了方便上网,都会采用openwrt的路由。 13 | 而在这套精简版的路由上面,并没有好的流量监控管理软件。 14 | 作者所在的活动室就处于这种情况的困扰,因此趁着这次比赛,实现了一个轻量级的流量监控管理系统 15 | 16 | ### Where 17 | * 平台: 18 | - OpenWrt 一款为路由定制的精简版Linux 19 | * 组件: 20 | - iptable Linux下最强大的防火墙,没有之一(自带) 21 | - /etc/config/wireless Openwrt下的MAC过滤配置文件(自带) 22 | - cron Linux下的定时管理任务守护进程(自带) 23 | Cron Config: 24 | * * * * * /bin/wrtbwmon setup br-lan 25 | * * * * * /bin/wrtbwmon update 26 | 3 1 1 * * /bin/wrtbwmon clear 27 | - uhttpd Openwrt下的web服务器 (opkg install uhttpd) 28 | - php5 php解析器 (opkg install php5) 29 | - php5-mod-json php的json处理模块 (opkg install php5-mod-json) 30 | 31 | 32 | ### How 33 | 在开发前,我们找到了wrtbwmon这个脚本,一个运行在Openwrt上的利用iptable进行流量统计的shell脚本。 34 | 在理解了wrtbwmon的工作机制之后,我们定义了一个json文件,结构参见文件data.json,作为该软件的数据结构。 35 | 我们hack了wrtbwmon的setup功能,实现了通过filter.txt实现内网流量的过滤。 36 | 我们hack了wrbwmon的update功能,将其输出按照我们定义的格式输入到data.json。 37 | 实现了sync的功能,对于data.json的未超流量的Mac地址同步到data.json。 38 | 实现了clear的功能,将data.json以日期备份然后将流量清零。 39 | 最后,我们实现一套web管理系统,便于管理员操作,实现用户和设备的增删改操作,并且触发后端的脚本进行同步。 40 | 41 | ### Who 42 | 西安电子科技大学 计算机学院 袁少鹏 43 | 西安电子科技大学 计算机学院 曾少剑 44 | 如果在使用中有任何问题,请email至ibirdyuan@gmail.com 45 | 46 | 47 | -------------------------------------------------------------------------------- /accept.php: -------------------------------------------------------------------------------- 1 | 152 | -------------------------------------------------------------------------------- /admin.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | $v) 26 | { 27 | $device_n = count($v['device']) + 1; 28 | $id = $k; 29 | $name = $v['name']; 30 | $total = (int)($v['total'] / 1024 / 1024); 31 | $nowflow = (int)($v['nowflow'] / 1024 / 1024); 32 | $total_flow += $v['nowflow']; 33 | 34 | $id_flow = $id . '_flow'; 35 | $id_type = $id . '_newtype'; 36 | $id_mac = $id . '_newmac'; 37 | 38 | echo ""; 39 | echo ""; 40 | echo ""; 41 | echo ""; 42 | echo ""; 43 | echo ""; 44 | echo ""; 45 | echo ""; 46 | echo ""; 47 | echo ""; 48 | echo ""; 49 | echo ""; 50 | foreach($json['club'][$k]['device'] as $key=>$value) 51 | { 52 | $type = $value['type']; 53 | $mac = $value['mac']; 54 | $out = (int)($value['out'] / 1024 / 1024); 55 | $in = (int)($value['in'] / 1024 / 1024); 56 | 57 | 58 | $id_type = $id.'_'.$key . '_type'; 59 | $id_mac = $id .'_'.$key . '_mac'; 60 | 61 | $id_key = $id . '_' . $key; 62 | 63 | $total_out += (int)$value['out']; 64 | $total_in += (int)$value['in']; 65 | echo ""; 66 | echo ""; 67 | echo ""; 68 | echo ""; 69 | echo ""; 70 | echo ""; 71 | echo ""; 72 | echo ""; 73 | 74 | } 75 | } 76 | 77 | $id = $id + 1; 78 | $total_flow = (int)($total_flow / 1024 / 1024); 79 | $total_out = (int)($total_out / 1024 / 1024); 80 | $total_in = (int)($total_in / 1024 / 1024); 81 | echo ""; 82 | echo ""; 83 | echo ""; 84 | echo ""; 85 | echo ""; 86 | echo ""; 87 | echo ""; 88 | echo ""; 89 | echo ""; 90 | echo ""; 91 | echo ""; 92 | echo ""; 93 | echo ""; 94 | echo ""; 95 | echo ""; 96 | ?> 97 |
姓名总流量(M)已用流量(M)设备名设备Mac地址上传量(M)下载量(M)操作
$name$total$nowflow
$type$mac$out$in
总计30720$total_flow$total_out$total_in
98 | 99 | 100 | -------------------------------------------------------------------------------- /data.json: -------------------------------------------------------------------------------- 1 | {"club":[ 2 | {"name":"\u5415\u4e2d","total":"3221225472","nowflow":"0","device":[{"type":"kindle","mac":"ae:23:3a:4a:43:21","in":"0","out":"0"},{"type":"pc","mac":"12:12:12:11:11:11","in":"0","out":"0"}]}, 3 | {"name":"\u738b\u4e39\u840c","total":"3221225472","nowflow":"0","device":[{"type":"pc","mac":"00:11:dd:23:e1:aa","in":"0","out":"0"}]}]} -------------------------------------------------------------------------------- /filter.txt: -------------------------------------------------------------------------------- 1 | 202.117.112.0/20 2 | 210.27.0.0/20 3 | 219.245.64.0/18 4 | 115.155.0.0/18 5 | 222.25.128.0/18 6 | 219.244.112.0/20 7 | 10.168.200.1 8 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var typeArr = ['pc', 'kindle', 'phone', 'laptop', 'others'] 2 | var flowArr = ['1','2','3','4','5'] 3 | var Ajax_URL = '/FControl/accept.php' 4 | var flag = 1 5 | 6 | //初始化select标签,参数为option的value值的数组 7 | var initSelect = function( arr ) 8 | { 9 | var select = document.createElement('select') 10 | for(i in arr) 11 | { 12 | var option = document.createElement('option') 13 | option.setAttribute('value', arr[i]) 14 | option.text = arr[i] 15 | select.appendChild(option) 16 | } 17 | return select 18 | } 19 | 20 | var checkMac = function(macaddr) 21 | { 22 | var patt = new RegExp('^([0-9a-f]{2}:){5}[0-9a-f]{2}$') 23 | return patt.test(macaddr) 24 | } 25 | 26 | var arrToStr = function(arr) 27 | { 28 | var str = '' 29 | for( x in arr ) 30 | { 31 | str += x + '=' 32 | str += arr[x] + '&' 33 | } 34 | str = str.slice(0,-1) 35 | return str 36 | } 37 | 38 | var checkFlag = function() 39 | { 40 | if (flag == 0) 41 | { 42 | alert('请确认执行完上一个操作!') 43 | return false 44 | } 45 | 46 | flag = 0 47 | return true 48 | 49 | } 50 | 51 | //ajax方法 52 | var ajax = function(url, f, string){ 53 | var xml = (window.XMLHttpRequest) ? (new XMLHttpRequest()) : (new ActiveXObject("Micorsoft.XMLHttpRequest")); 54 | xml.onreadystatechange = function(){ 55 | f(xml.responseText); 56 | } 57 | 58 | xml.open("POST",url,true); 59 | xml.setRequestHeader("Content-type","application/x-www-form-urlencoded"); 60 | xml.send(string); 61 | } 62 | 63 | //替换节点 64 | var changeId = function(id, newtype) 65 | { 66 | var old = document.getElementById(id) 67 | if(old.firstChild != null) 68 | { 69 | old.removeChild(old.firstChild) 70 | } 71 | old.appendChild(newtype) 72 | } 73 | 74 | //新增设备 75 | var addDevice = function(obj) 76 | { 77 | if(!checkFlag())return; 78 | 79 | var id = obj.name 80 | var oldtype = id + '_newtype' 81 | var oldmac = id + '_newmac' 82 | 83 | arr = {'method':'addDevice','id':id, 'node':{'device':oldtype,'macaddr':oldmac}} 84 | changeId(oldtype, initSelect(typeArr)) 85 | 86 | var input =document.createElement('input') 87 | input.setAttribute('type' , 'text') 88 | 89 | changeId(oldmac, input) 90 | obj.setAttribute('value','确定') 91 | obj.setAttribute('onclick','Submit(arr)') 92 | } 93 | 94 | //新增用户 95 | var addUser = function(obj) 96 | { 97 | if(!checkFlag())return; 98 | 99 | var input =document.createElement('input') 100 | input.setAttribute('type' , 'text') 101 | changeId('newname', input) 102 | 103 | changeId('newflow', initSelect(flowArr)) 104 | 105 | arr = {'method':'addUser','node':{'name':'newname','total':'newflow'}} 106 | 107 | obj.setAttribute('value','确定') 108 | obj.setAttribute('onclick','Submit(arr)') 109 | 110 | } 111 | //更改流量 112 | var changeFlow = function(obj) 113 | { 114 | if(!checkFlag())return; 115 | 116 | id = obj.name 117 | oldflow = id + '_flow' 118 | 119 | arr = {'method':'changeFlow','id':id, 'node':{'flow':oldflow}} 120 | 121 | changeId(oldflow, initSelect(flowArr)) 122 | 123 | obj.setAttribute('value','确定') 124 | obj.setAttribute('onclick','Submit(arr)') 125 | } 126 | 127 | 128 | //修改Mac 129 | var changeMac = function(obj) 130 | { 131 | if(!checkFlag())return; 132 | 133 | var id = obj.name 134 | var oldmac = id + '_mac' 135 | var oldtype = id + '_type' 136 | 137 | arr = {'method':'changeMac','id':id, 'node':{'macaddr':oldmac}} 138 | 139 | var input =document.createElement('input') 140 | input.setAttribute('type' , 'text') 141 | input.setAttribute('value', document.getElementById(oldmac).firstChild.nodeValue) 142 | changeId(oldmac, input) 143 | obj.setAttribute('value','确定') 144 | obj.setAttribute('onclick','Submit(arr)') 145 | } 146 | 147 | //删除用户 148 | var delUser = function(obj){ 149 | if(!checkFlag())return; 150 | 151 | var r = confirm("您确定要删除该设备?") 152 | if (r == true) 153 | { 154 | var arr = {} 155 | arr['method'] = 'delUser' 156 | arr['id'] = obj.name 157 | string = arrToStr(arr) 158 | ajax(Ajax_URL,check,string) 159 | } 160 | flag = 1 161 | } 162 | 163 | //删除设备 164 | var delDevice = function(obj){ 165 | if(!checkFlag())return; 166 | 167 | var r = confirm("您确定要删除该设备?") 168 | if (r == true) 169 | { 170 | var arr = {} 171 | arr['id'] = obj.name 172 | arr['method'] = 'delDevice' 173 | string = arrToStr(arr) 174 | ajax(Ajax_URL,check,string) 175 | } 176 | flag = 1 177 | } 178 | 179 | var Submit = function(arr) 180 | { 181 | var send = {} 182 | if(arr['id']) 183 | { 184 | send['id'] = arr['id'] 185 | } 186 | send['method'] = arr['method'] 187 | for(x in arr['node']) 188 | { 189 | var node = document.getElementById(arr['node'][x]) 190 | text = node.firstChild.value 191 | send[x] = text 192 | } 193 | for(x in send) 194 | { 195 | if(x == 'macaddr' && checkMac(send[x]) == false) 196 | { 197 | alert('请检查mac地址的格式') 198 | return; 199 | } 200 | } 201 | string = arrToStr(send) 202 | ajax(Ajax_URL,check,string) 203 | flag = 1 204 | } 205 | 206 | //清除流量后通过ajax方法提交 207 | var cleanFlow = function() 208 | { 209 | string = 'method=cleanFlow' 210 | ajax(Ajax_URL, check, string) 211 | flag = 1 212 | } 213 | 214 | var check = function(response){ 215 | setTimeout('window.location.assign(window.location.href)',2000) 216 | } 217 | 218 | -------------------------------------------------------------------------------- /index.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | $v) 25 | { 26 | $device_n = count($v['device']) + 1; 27 | $id = $k; 28 | $name = $v['name']; 29 | $total = (int)($v['total'] / 1024 / 1024); 30 | $nowflow = (int)($v['nowflow'] / 1024 / 1024); 31 | $total_flow += $v['nowflow']; 32 | 33 | $id_flow = $id . '_flow'; 34 | $id_type = $id . '_newtype'; 35 | $id_mac = $id . '_newmac'; 36 | 37 | echo ""; 38 | echo ""; 39 | echo ""; 40 | echo ""; 41 | echo ""; 42 | foreach($json['club'][$k]['device'] as $key=>$value) 43 | { 44 | $type = $value['type']; 45 | $mac = $value['mac']; 46 | $out = (int)($value['out'] / 1024 / 1024); 47 | $in = (int)($value['in'] / 1024 / 1024); 48 | 49 | 50 | $id_type = $id.'_'.$key . '_type'; 51 | $id_mac = $id .'_'.$key . '_mac'; 52 | 53 | $id_key = $id . '_' . $key; 54 | 55 | $total_out += (int)$value['out']; 56 | $total_in += (int)$value['in']; 57 | echo ""; 58 | echo ""; 59 | echo ""; 60 | echo ""; 61 | echo ""; 62 | echo ""; 63 | 64 | } 65 | } 66 | 67 | $id = $id + 1; 68 | $total_flow = (int)($total_flow / 1024 / 1024); 69 | $total_out = (int)($total_out / 1024 / 1024); 70 | $total_in = (int)($total_in / 1024 / 1024); 71 | echo ""; 72 | echo ""; 73 | echo ""; 74 | echo ""; 75 | echo ""; 76 | echo ""; 77 | echo ""; 78 | echo ""; 79 | ?> 80 |
姓名总流量(M)已用流量(M)设备名设备Mac地址上传量(M)下载量(M)
$name$total$nowflow
$type$mac$out$in
总计30720$total_flow$total_out$total_in
81 | 82 | 83 | -------------------------------------------------------------------------------- /wrtbwmon: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # Traffic logging tool for OpenWRT-based routers 4 | # 5 | # Created by Emmanuel Brucy (e.brucy AT qut.edu.au) 6 | # 7 | # This program is free software; you can redistribute it and/or 8 | # modify it under the terms of the GNU General Public License 9 | # as published by the Free Software Foundation; either version 2 10 | # of the License, or (at your option) any later version. 11 | # 12 | # This program is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with this program; if not, write to the Free Software 19 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 20 | # 21 | # Hacked by Ibird Yuan and Zeng ShaoJian 22 | # We Make it to update to 'data.json' and sync to '/etc/config/wireless' to # control the traffic. 23 | 24 | LAN_IFACE=${2} || $(nvram get lan_ifname) 25 | 26 | WAN='eth1.1' 27 | 28 | # Get the filter file 29 | # For example, we can filter the intranet traffic 30 | 31 | FILTER='/www/FControl/filter.txt' 32 | JSON='/www/FControl/data.json' 33 | WIRELESS='/etc/config/wireless' 34 | 35 | if [ ! -f $JSON ];then 36 | echo "The Json file is missing!" 37 | exit 38 | fi 39 | 40 | if [ ! -f $WIRELESS ];then 41 | echo "The Wireless file is missing" 42 | exit 43 | fi 44 | 45 | #use SED to annotate the mac address in the file 46 | #Make the mac address unwork 47 | del_mac () 48 | { 49 | mac=$1 50 | if [ -z $mac ];then 51 | echo "Need Macaddress" 52 | exit 53 | fi 54 | #del mac user 55 | sed -i "/$mac/ s/\tlist/#list/" $WIRELESS 56 | 57 | } 58 | 59 | case ${1} in 60 | 61 | "setup" ) 62 | 63 | #Create the RRDIPT CHAIN (it doesn't matter if it already exists). 64 | iptables -N RRDIPT 2> /dev/null 65 | 66 | #Create the FILTER CHAIN (it doesn't matter if it already exists). 67 | iptables -N FILTER 2> /dev/null 68 | 69 | #Add the FILTER CHAIN to the FORWARD chain (if non existing). 70 | iptables -L FORWARD --line-numbers -n | grep "FILTER" | grep "1" > /dev/null 71 | if [ $? -ne 0 ]; then 72 | iptables -L FORWARD -n | grep "FILTER" > /dev/null 73 | if [ $? -ne 0 ]; then 74 | iptables -D FORWARD -j FILTER 75 | fi 76 | iptables -I FORWARD -j FILTER 77 | fi 78 | 79 | 80 | #Add the RRDIPT CHAIN to the FORWARD chain (if non existing). 81 | iptables -L FORWARD --line-numbers -n | grep "RRDIPT" | grep "2" > /dev/null 82 | if [ $? -ne 0 ]; then 83 | iptables -L FORWARD -n | grep "RRDIPT" > /dev/null 84 | if [ $? -ne 0 ]; then 85 | iptables -D FORWARD -j RRDIPT 86 | fi 87 | iptables -I FORWARD 2 -j RRDIPT 88 | fi 89 | 90 | #For each ip in the filter table 91 | [ -f $FILTER ] && cat $FILTER |while read IP ; 92 | do 93 | iptables -nL FILTER |grep "${IP}" > /dev/null 94 | if [ $? -ne 0 ]; then 95 | iptables -I FILTER -s ${IP} -j ACCEPT 96 | iptables -I FILTER -d ${IP} -j ACCEPT 97 | fi 98 | done 99 | 100 | #For each host in the ARP table 101 | grep ${LAN_IFACE} /proc/net/arp | while read IP TYPE FLAGS MAC MASK IFACE ; 102 | do 103 | #Add iptable rules (if non existing). 104 | iptables -nL RRDIPT | grep "${IP} " > /dev/null 105 | if [ $? -ne 0 ]; then 106 | iptables -I RRDIPT -d ${IP} -j RETURN 107 | iptables -I RRDIPT -s ${IP} -j RETURN 108 | fi 109 | done 110 | ;; 111 | 112 | "update" ) 113 | 114 | #Read and reset counters 115 | traffic=`iptables -L RRDIPT -vnxZ -t filter ` 116 | 117 | grep -v "0x0" /proc/net/arp | grep -v 'IP address' | grep -v $WAN | while read IP TYPE FLAGS MAC MASK IFACE 118 | do 119 | #Add new data to the graph. Count in Kbs to deal with 16 bits signed values (up to 2G only) 120 | #Have to use temporary files because of crappy busybox shell 121 | echo "$traffic" | grep ${IP} | while read PKTS BYTES TARGET PROT OPT IFIN IFOUT SRC DST 122 | do 123 | [ "${DST}" = "${IP}" ] && echo ${BYTES} > /tmp/in.tmp 124 | [ "${SRC}" = "${IP}" ] && echo ${BYTES} > /tmp/out.tmp 125 | done 126 | 127 | IN=$(cat /tmp/in.tmp) 128 | OUT=$(cat /tmp/out.tmp) 129 | rm -f /tmp/in.tmp 130 | rm -f /tmp/out.tmp 131 | 132 | if [ ${IN} -gt 0 -o ${OUT} -gt 0 ]; then 133 | echo "DEBUG : New traffic for ${MAC} since last update : ${IN}k:${OUT}k" 134 | 135 | # compute the number of the user's mac address 136 | # detail see the data.json 137 | nf=0 138 | content=`grep ${MAC} $JSON` 139 | nf=`echo $content | tr ',' '\n' |grep -c 'mac'` 140 | nf=$((nf*4+4)) 141 | i=5 142 | 143 | # get the mac address's old flow 144 | while [ $i -lt $nf ] 145 | do 146 | line=`echo $content | cut -d, -f$i | grep ${MAC} | wc -l` 147 | if [ $line -eq 1 ] ;then 148 | in=$(echo ${content} | cut -d, -f$((i+1)) | awk -F\" '{print $4}' ) 149 | out=$(echo ${content} | cut -d, -f$((i+2)) | awk -F\" '{print $4}') 150 | break 151 | fi 152 | i=$((i+4)) 153 | done 154 | PEAKUSAGE_IN=$((in+IN)) 155 | PEAKUSAGE_OUT=$((out+OUT)) 156 | 157 | nowflow=`echo $content|grep $MAC|tr ',:"' ' ' |awk '{print $7}'` 158 | nowflow=$((nowflow+OUT+IN)); 159 | 160 | #update nowflow and in and out flow to the file 161 | sed -i "/$MAC/ s/nowflow\":\"[0-9]*\"/nowflow\":\"$nowflow\"/" $JSON 162 | sed -i "s/\"${MAC}\",\"in\":\"$in\",\"out\":\"$out\"/\"${MAC}\",\"in\":\"${PEAKUSAGE_IN}\",\"out\":\"${PEAKUSAGE_OUT}\"/" $JSON 163 | fi 164 | done 165 | 166 | # Free some memory 167 | rm -f /tmp/*_$$.tmp 168 | ;; 169 | 170 | "clear" ) 171 | #bake-up the file 172 | cp $JSON $JSON.`date -I` 173 | #clear 174 | sed -i "s/nowflow\":\"[0-9]*\"/nowflow\":\"0\"/" $JSON 175 | sed -i "s/in\":\"[0-9]*\",\"out\":\"[0-9]*\"/in\":\"0\",\"out\":\"0\"/g" $JSON 176 | 177 | #sync 178 | sed -i "s/^#//" $WIRELESS 179 | 180 | ;; 181 | 182 | "sync" ) 183 | cp $WIRELESS $WIRELESS.bak 184 | config=`grep -v 'list maclist' $WIRELESS.bak > $WIRELESS` 185 | for line in `grep 'name' $JSON` 186 | do 187 | total=`echo $line | cut -d, -f2 | awk -F\" '{print $4}'` 188 | nowflow=`echo $line | cut -d, -f3 | awk -F\" '{print $4}'` 189 | nf=`echo $line |tr ',' '\n'| grep -c 'mac'` 190 | i=5 191 | nf=$((nf*4+4)) 192 | while [ $i -lt $nf ] 193 | do 194 | mac=`echo $line | cut -d, -f$i | awk -F\" '{print $4}'` 195 | if [ $total -gt $nowflow ];then 196 | echo -e "\tlist maclist $mac" >> $WIRELESS 197 | else 198 | echo -e "#\tlist maclist $mac" >> $WIRELESS 199 | fi 200 | i=$((i+4)) 201 | done 202 | done 203 | rm -f $WIRELESS.bak 204 | (wifi down;wifi up)& 205 | ;; 206 | 207 | *) 208 | echo "Usage : $0 {setup|update|clear|sync} [options...]" 209 | echo "Options : " 210 | echo " $0 setup" 211 | echo " $0 update" 212 | echo " $0 clear" 213 | echo " $0 sync" 214 | echo "Examples : " 215 | echo " $0 setup br-lan" 216 | echo " $0 update" 217 | echo " $0 clear" 218 | echo " $0 sync" 219 | exit 220 | ;; 221 | esac 222 | 223 | --------------------------------------------------------------------------------