SCADA
За да реализираме една SCADA система, най-напред трябва да помислим за комуникацията с различни видове PLC. На пазара за контролери се срещат най-различни производители, предлагащи уреди с най-различни характеристики и десетки комуникационни протоколи. В почти всички случаи данните, които трябва да четем от контролера са малко по обем, в сравнение с паметта на съвременните компютри. Традиционно комуникацията с контролерите е трудна, понеже те могат да бъдат разпръснати на голяма територия. Този проблем е довел до използването през годините на най-различни комуникационни протоколи и преносни среди – голяма част от които изглеждат доста глупаво от съвременна гледна точка. Но тъй като в повечето случаи става дума за производствени процеси – на никой не му и минава през ума да променя каквото и да било. Тоест, принудени сме да работим при тези условия. Когато започнем работа, всякакви предположения за марката контролер и комуникационния протокол, след време ще се окажат неверни (по законите на Мърфи). Можем да се сблъскаме с всичко – от 9600 bps RS232 връзка, с ASCII протокол, до ISO върху Industrial Ethernet. Често пъти библиотеките за комуникация с контролерите не са с отворен код (за щастие при мен случая не е този). Можем да се надяваме, че те предлагат някакъв C интерфейс, който ние да ползваме от някакъв външна програма. Една от първите ни задачи е да реализираме универсален интерфейс към контролерите, който да ни позволява да четем и пишем в тях, без значение каква марка са и какъв протокол използват. Лично аз, дълго време живеех с убеждението, че е най-добре просто да използвам асинхронна комуникация с event loop. Това щеше да работи, ако “драйверите” за различните видове контролери се държаха добре и не променяха настройките на подаваните им файлови дескриптори. За съжаление, това е прекалено оптимистично предположение. Повечето контролери са ужасно бавни, в сравнение със съвременните процесори. Данните могат да се точат “дълго време”, а буферирането в ядрото може да забави получаването им от програмата. Понеже комуникацията се очаква да бъде в “реално време”, а реализацията на различните специфични протоколи върху TCP/IP например, е сложна – можем да очакваме, че голяма част от тези драйвери ползват блокираща, синхронна комуникация. Очевидно блокирането на event loop при отпадане на контролер не е добра идея (особено ако работим със стотици контролери). След обстоен анализ на ситуацията и кратка справка с някои исторически системи, аз се спрях на Reactor pattern за решаване на този проблем. Тъй като искаме да пишем на Ruby, нашата Reactor pattern реализация е EventMachine. За да я ползваме, трябва да напишем Ruby bindings за драйверите на контролерите. Това е доста досадна и неблагодарна работа, но за щастие разполагаме с Extending Ruby главата от Programming Ruby. След няколко дни писане имаме работещи Test::Unit::TestCase(s), които събират данни от контролерите. Как обаче да използваме нашия binding с EventMachine? В общия случай имаме клас PLC, в който има един гаден метод, например fetch, реализиран на C, който може да блокира. EventMachine ни дава метод defer, той приема два аргумента – процедури. Първата процедура е блокираща, тя се изпълнява на отделен thread в реактора, а втората е callback – извиква се с резултата от изпълниение на първата процедура (изпълнява се извън реактора). EventMachine ни дава възможност да работим с нишки индиректно, като по този начин си спестяваме всичките проблеми свързани с директното им използване. Ето една примерна конструкция с defer:
require 'rubygems'
require 'eventmachine'
# Контролер
class PLC
# Голям, блокиращ C метод
def fetch
"result"
end
end
plc = PLC.new
EM.run do
# Тази процедура ще изпълняваме в реактора, на отделна нишка
# Ако всичко е наред, ще четем през една секунда паметта на контролера
op = proc { sleep 1; plc.fetch }
# Тук обработваме резултата, и пускаме следващата операция за четене
cb = proc { |result| puts result; EM.defer(op, cb) }
# Пускаме първата операция
EM.defer(op, cb)
endЕстествено, за да работи всичко това, блокиращата операция трябва да има разумен timeout. Той зависи силно от вида контролер, комуникационния протокол и наблюдавания производствен процес. При мен този timeout е от порядъка на 5 секунди. Тъй като EventMachine е реализиран на C/C++, нашия драйвер за контролера също е писан на C, можем да очакваме, че закъсненията от използването на Ruby – динамичен език от високо ниво, са минимални. Гаранции за това обаче няма, много е важно всички компоненти да бъдат тествани много подробно, преди да бъдат пуснати в експлоатация. Официално, системата която аз разработвам се пише на Ruby, но реално зад всеки един ред код на Ruby стоят между 10 и 100 реда код на C. За съжаление, това в случая е неизбежно. Прочетените от контролерите данни отразяват състоянието на реалните обекти, които участват в производствения процес. Връзката между обекти и контролери е основополагаща за всички подобни системи – важно е да имаме максимално гъвкави методи за описание на тази връзка. За целта използваме DSL, който лесно може да бъде реализиран на Ruby. След като имаме добре дефинирани обекти, техните състояния и събитията, които водят до промяната им, можем да започнем да мислим за визуализацията им. За подобна система, би било много глупаво да не използваме Web-базирана технология. Тъй като изображенията на обектите са инженерни чертежи, които трябва да бъдат скалируеми, не можем да ползваме нищо друго, освен SVG. Във всяко едно SVG изображение има определено количество графични обекти. Всеки графичен обект може да бъде в едно от няколко изброени състояния, което се променя във времето. За да визуализираме графиката използваме WebKit прозорец. Този прозорец се създава с помощта на QtRuby, като за целта (леко) се променя WebKit конфигурацията. За да следим измененията на състоянията на обектите във времето трябва да използваме, някакъв вид асинхронна комуникация с WebKit. Тъй като събитията се случват на сървъра, използването на XMLHttpRequest заявки (по-известни като AJAX) не е достатъчно. Клиентът няма как да знае кога дадено събитие се е случило, за да пусне заявката. Разбира се, можем да ползва polling, но е далеч по-елегантно комуникацията да бъде асинхронна. Това което ни трябва е известно, като Reverse AJAX, HTTP Server Push или Comet). За да реализираме каквото и да било Web-приложение, трябва да имаме HTTP сървър. Естествено, той може да бъде нещо съвсем отделно и външно за системата, но тъй като така или иначе, ние имаме работещ EventMachine event loop, че даже и реактор, за комуникация с контролерите – ориентираме се към търсене на EventMachine-базиран HTTP сървър. Както може да се очаква, някой преди нас е написал такъв, казва се Thin. Можем ли да използваме Thin и EventMachine за Comet комуникация с WebKit? Върху този върпос е размишлявал и автора на Thin. Той е написал един Ruby gem, който се казва Pusher и върши точно това което ни трябва. Благодарение на тези технологии, можем да обединим функционалността на HTTP сървъра, Comet сървъра, и модула за комуникация с контролерите. Разбира се, това е просто възможност за интеграция, в някои случаи не е разумно да се прави. Възможно е да “сглобяваме” SVG документите динамично, при всички случаи е трябва да имаме някакво Web приложение на сървъра, което да обслужва заявики към различни URL адреси. Най-удобно за целта можем да използваме Sinatra, или още по-добре Async Sinatra. Вече можем да доставяме SVG документите до клиентите, и да правим асинхронен “push” на съобщения към тях. Това което ще им изпращаме може да бъде XML или JSON съобщение (всъщност може да е всичко). Но най-ефективно от страна на браузера се обработват JSON съобщения, понеже те са просто сериализирани JavaScript обекти. При получаване на JSON съобщение, някакъв JavaScript в браузера трябва да намира елемента от документа, към който се отнася съобщението и да сменя някакви негови атрибути (или да го заменя с друг елемент). В HTML документите тези неща стават много лесно, благодарение на DOM стандарта, както и популярните JavaScript библиотеки от сорта на Prototype, Dojo и т.н., които го разширяват. Тези библиотеки не са много наясно с SVG и различните негови елементи, които могат да се срещнат в документа. Спасението в този случай беше библиотеката Raphaël, тя разширява DOM за SVG документи.
Мисля, че преди време Кнут беше казал, че съвременното програмиране все повече прилича на сглобяване на пъзел. В работата си по този проект, отделих адски много време за проучване на различни, вече реализирани библиотеки и технологии и много малко време за същинско писане на код. Но това не е задължително да бъде лошо.
iPhone hacks 2
iPhone:~ root# gcc -v Using built-in specs. Target: arm-apple-darwin9 Configured with: ../llvm-gcc-4.2/configure --build=x86_64-unknown-linux-gnu --host=arm-apple-darwin9 --enable-static=no --enable-shared=yes --prefix=/usr --localstatedir=/var/cache/iphone-gcc --enable-languages=c,c++,objc,obj-c++ --enable-wchar_t=no --with-gxx-include-dir=/usr/include/c++/4.0.0 Thread model: posix gcc version 4.2.1 (Based on Apple Inc. build 5555)
iPhone:~ root# cat hello.c #includeint main() { printf("hello\n"); return 0; } iPhone:~ root# gcc hello.c iPhone:~ root# ./a.out Killed iPhone:~ root# dmesg | tail -1 seatbelt: hook..execve() killing pid 242: outside of container && !i_can_has_debugger iPhone:~ root# ldid -S a.out iPhone:~ root# ./a.out hello
iPhone:~ root# uname -a Darwin iPhone 9.4.1 Darwin Kernel Version 9.4.1: Sat Nov 1 19:09:48 PDT 2008; root:xnu-1228.7.36~2/RELEASE_ARM_S5L8900X iPhone1,2 arm N82AP Darwin
iPhone:~ root# sysctl hw hw.ncpu: 1 hw.byteorder: 1234 hw.memsize: 121634816 hw.activecpu: 1 hw.optional.floatingpoint: 1 hw.packages: 1 hw.tbfrequency: 6000000 hw.fixfrequency: 24000000 hw.prffrequency_max: 51500000 hw.prffrequency_min: 51500000 hw.prffrequency: 51500000 hw.memfrequency_max: 137333333 hw.memfrequency_min: 137333333 hw.memfrequency: 137333333 hw.l1dcachesize: 16384 hw.l1icachesize: 16384 hw.cachelinesize: 32 hw.cpufrequency_max: 412000000 hw.cpufrequency_min: 412000000 hw.cpufrequency: 412000000 hw.busfrequency_max: 103000000 hw.busfrequency_min: 103000000 hw.busfrequency: 103000000 hw.pagesize: 4096 hw.cachesize: 0 0 0 0 0 0 0 0 0 0 hw.cacheconfig: 0 16384 16384 1 32 4 0 0 0 0 hw.cpufamily: -1879695144 hw.cpu64bit_capable: 0 hw.cpusubtype: 6 hw.cputype: 12 hw.logicalcpu_max: 1 hw.logicalcpu: 1 hw.physicalcpu_max: 1 hw.physicalcpu: 1 hw.machine = iPhone1,2 hw.model = N82AP hw.ncpu = 1 hw.byteorder = 1234 hw.physmem = 121634816 hw.usermem = 91435008 hw.pagesize = 4096 hw.epoch = 1 hw.vectorunit = 0 hw.busfrequency = 103000000 hw.cpufrequency = 412000000 hw.cachelinesize = 32 hw.l1icachesize = 16384 hw.l1dcachesize = 16384 hw.tbfrequency = 6000000 hw.memsize = 121634816 hw.availcpu = 1
iPhone:~ root# objdump -D a.out | head a.out: file format mach-o-le Disassembly of section LC_SEGMENT.__TEXT: 0000000000001000: 1000: feedface cdp2 10, 14, cr15, cr13, cr14, {6} 1004: 0000000c andeq r0, r0, ip 1008: 00000000 andeq r0, r0, r0 100c: 00000002 andeq r0, r0, r2
iPhone:~ root# df -h Filesystem Size Used Avail Use% Mounted on /dev/disk0s1 523M 489M 29M 95% / devfs 25K 25K 0 100% /dev /dev/disk0s2 7.1G 1.2G 6.0G 16% /private/var
iPhone:~ root# ps ax
PID TT STAT TIME COMMAND
1 ?? Ss 0:02.60 /sbin/launchd
12 ?? Ss 0:01.55 /usr/sbin/mDNSResponder -launchd
13 ?? Ss 0:02.38 /usr/sbin/notifyd
14 ?? Ss 0:03.03 /usr/sbin/syslogd
15 ?? Ss 0:13.70 /usr/sbin/configd
17 ?? Ss 2:07.40 /System/Library/CoreServices/SpringBoard.app/SpringBoard
18 ?? Ss 0:00.70 /usr/bin/yellowsn0w
20 ?? Ss 0:00.44 /Applications/MxTube.app/MxT2d
22 ?? Ss 0:00.72 /usr/sbin/update
23 ?? Ss 0:28.78 /usr/libexec/lockdownd
24 ?? Ss 0:32.73 /usr/sbin/mediaserverd
26 ?? Ss 0:02.83 /System/Library/PrivateFrameworks/IAP.framework/Support/iapd
27 ?? Ss 0:00.29 /usr/sbin/fairplayd
30 ?? Ss 0:07.88 /System/Library/PrivateFrameworks/CoreTelephony.framework/Support/CommCenter
31 ?? Ss 0:01.11 /usr/sbin/BTServer
43 ?? Ss 0:03.12 /Applications/MobilePhone.app/MobilePhone
77 ?? Ss 0:10.22 /Applications/MobileMail.app/MobileMail
80 ?? Ss 0:58.92 /Applications/MobileSafari.app/MobileSafari
100 ?? Ss 0:00.59 /System/Library/Frameworks/SystemConfiguration.framework/SCHelper
105 ?? Ss 0:00.27 /usr/libexec/ptpd -t usb
110 ?? S 0:00.38 /usr/libexec/afcd --lockdown -d /var/mobile/Media -u mobile
112 ?? S 0:00.11 /usr/libexec/notification_proxy
230 ?? S 0:01.48 /usr/sbin/sshd -i
271 ?? Ss 0:00.11 /usr/libexec/securityd
231 s000 Ss 0:00.42 -sh
272 s000 R+ 0:00.02 ps axiPhone:/ root# nmap 192.168.1.1 Starting Nmap 4.76 ( http://nmap.org ) at 2009-04-02 22:26 EEST mass_dns: warning: Unable to open /etc/resolv.conf. Try using --system-dns or specify valid servers with --dns-servers mass_dns: warning: Unable to determine any DNS servers. Reverse DNS is disabled. Try using --system-dns or specify valid servers with --dns-servers Interesting ports on 192.168.1.1: Not shown: 997 filtered ports PORT STATE SERVICE 80/tcp open http 1900/tcp closed upnp 8080/tcp open http-proxy MAC Address: xx:xx:xx:xx:xx:xx (Tp-link Technologies Co.) Nmap done: 1 IP address (1 host up) scanned in 16.17 seconds
iPhone:~/tinyscheme1.39 root# make gcc -I. -c -Os scheme.c gcc -I. -c -Os dynload.c gcc -shared scheme.o dynload.o -ldl gcc -o scheme -Os scheme.o dynload.o -ldl iPhone:~/tinyscheme1.39 root# ldid -S scheme iPhone:~/tinyscheme1.39 root# ls -al scheme -rwxr-xr-x 1 root staff 46224 Apr 2 22:37 scheme* iPhone:~/tinyscheme1.39 root# ./scheme TinyScheme 1.39 > (eval (car (list (quote (display "scheme baby!"))))) scheme baby!#t
iPhone:~ root# apt-get update Ign http://www.ispaziorepo.com ./ Release.gpg Ign http://apt9.ihazsupper.com ./ Release.gpg Get:1 http://apt.bigboss.us.com stable Release.gpg [186B] Ign http://apt9.yellowsn0w.com ./ Release.gpg Hit http://www.ispaziorepo.com ./ Release Get:2 http://apt.modmyi.com stable Release.gpg [189B] Get:3 http://repo.smxy.org xena Release.gpg [189B] Ign http://apt9.ihazsupper.com ./ Release Ign http://mspasov.com stable Release.gpg Get:4 http://www.zodttd.com stable Release.gpg [189B] Ign http://www.ispaziorepo.com ./ Packages/DiffIndex Get:5 http://apt.saurik.com tangelo Release.gpg [189B] Ign http://apt9.yellowsn0w.com ./ Release Hit http://apt.bigboss.us.com stable Release Get:6 http://apt.modmyi.com stable Release [483B] Ign http://apt9.ihazsupper.com ./ Packages/DiffIndex Hit http://repo.smxy.org xena Release Ign http://apt9.yellowsn0w.com ./ Packages/DiffIndex Hit http://www.ispaziorepo.com ./ Packages Hit http://www.zodttd.com stable Release Get:7 http://apt.saurik.com tangelo Release [566B] Get:8 http://mspasov.com stable Release [2938B] Hit http://apt9.ihazsupper.com ./ Packages Hit http://apt9.yellowsn0w.com ./ Packages Ign http://mspasov.com stable/main Packages/DiffIndex Ign http://apt.bigboss.us.com stable/main Packages/DiffIndex Ign http://apt.modmyi.com stable/main Packages/DiffIndex Ign http://apt.bigboss.us.com stable/main Packages Hit http://repo.smxy.org xena/main Packages/DiffIndex Get:9 http://mspasov.com stable/main Packages [1753B] Hit http://www.zodttd.com stable/main Packages/DiffIndex Ign http://apt.modmyi.com stable/main Packages Hit http://apt.bigboss.us.com stable/main Packages Ign http://apt.saurik.com tangelo/main Packages/DiffIndex Hit http://www.zodttd.com stable/main Packages Hit http://apt.modmyi.com stable/main Packages Hit http://apt.saurik.com tangelo/main Packages Fetched 5745B in 4s (1260B/s) Reading package lists... Done
SSH трикове
От извесно време насам ми се върти идеята да започна да пиша на тема компютърни трикове. Причината за това е, че съм почнал да забравям нещата, с които не се занимавам всекидневно. Има работи, които преди години правех с лекота, а в момента дори не мога да си спомня начина, по който съм ги правил. Естествено по-голямата част от тях съм ги намирал в Интернет, но за да ги намеря пак са необходими усилия. Дали тези усилия са по-големи от описването им тук – тепърва ще експериментирам. И така, няма по-универсален инструмент от OpenSSH, ползвам го навсякъде. Ето някои основни неща, които правя с него:
- Backup на файлови системи
Идеята е проста – четем файловата система с dump, архивираме данните през тръбата с gzip, подаваме към ssh връзката, а от другата страна ги поемаме с dd. Важно е да не бавим много и двете машини, по тази причина ползваме gzip за архиватор и blowfish за криптиране на връзката (няма по-бърз и толкова сигурен шифър за x86).
/sbin/dump -L0 -h0 -u -f - / | /usr/bin/gzip -c | /usr/bin/ssh -c blowfish backup@marvin dd of=eddie-root-backup-`date "+%d.%m.%Y"`.dump.gz
- Отдалечен http proxy сървър
ssh -L 3128:127.0.0.1:3128 eddie
По този начин пренасочваме порт 3128 към същия порт на отдалечената машина, през ssh връзката. Интересното в случая е, че този порт там слуша само на localhost интерфейса. Остава да се пусне proxy сървър на този порт (може и нещо друго да се пусне). Един добър избор е 3APA3A tiny proxy – малък е и има много проста конфигурация:
daemon auth none external 10.0.0.1 internal 127.0.0.1 proxy
ssh тунела ни спестява автентикация в proxy сървъра, както и един отворен порт на отдалечената машина. Връзката между браузера е proxy сървъра е криптирана.
- Отадлечени X клиенти или QEMU
Как да инсталираме Windows в QEMU на отдалечен компютър, до който имаме само ssh? Ами през ssh естествено:
Ще видим добре познатия син прозорец с инсталацията на Windows. Естествено трябва да имаме работещ X, windows.img файла трябва да е достатъчно голям, а winxp.iso трябва да бъде инсталационния диск. По същия начин можем да пуснем произволен X клиент, като връзката с нашия сървър е криптирана през ssh и никакви отворени портове не ни трябват.ssh -YC marvin qemu -localtime -hda windows.img -cdrom winxp.iso -boot d -net nic,model=rtl8139 -net tap,script=tapup.sh -m 256
- Закачване на отдалечена файлова система
В повечето случаи, особено когато не работим в един LAN, NFS не е нужен. Благодарение на FUSE, можем да закачим файлова система на отдалечена машина през ssh връзка. От извесно време работя с MacFUSE и ползвам често отдалечени файлови системи на доста машини.
sshfs marvin: /marvin
- Escape sequences
По този начин казваме, че символа след ~ ще се използва за управляващ символ към ssh. Различните управляващи символи са описани в документацията. Използването им е удобно и пести време.
ssh -e \~ marvin
