Revert "Version 2.3.1"

This reverts commit 3d4179ce84.
This commit is contained in:
Jeroen Oudshoorn
2023-09-08 20:26:42 +02:00
parent e5960aac59
commit 6f9649834a
2 changed files with 65 additions and 69 deletions

View File

@ -13,10 +13,9 @@ API_ADDRESS = "http://127.0.0.1:8666/api/v1"
def is_connected(): def is_connected():
try: try:
# check DNS # check DNS
host = socket.gethostbyname('api.pwnagotchi.ai') host = 'https://api.opwngrid.xyz/api/v1/uptime'
if host: r = requests.get(host, headers=None, timeout=(30.0, 60.0))
# check connectivity itself if r.json().get('isUp'):
socket.create_connection((host, 443), timeout=30)
return True return True
except: except:
pass pass

View File

@ -15,12 +15,12 @@ from pwnagotchi.ui.view import BLACK
import pwnagotchi.ui.fonts as fonts import pwnagotchi.ui.fonts as fonts
class Fix_Services(plugins.Plugin): class Fix_BRCMF(plugins.Plugin):
__author__ = 'xBits' __author__ = 'xBits'
__version__ = '0.1.1' __version__ = '0.1.1'
__license__ = 'GPL3' __license__ = 'GPL3'
__description__ = 'Fix blindness, firmware crashes and brain not being loaded' __description__ = 'Fix blindness, firmware crashes and brain not being loaded'
__name__ = 'Fix_Services' __name__ = 'Fix_BRCMF'
__help__ = """ __help__ = """
Reload brcmfmac module when blindbug is detected, instead of rebooting. Adapted from WATCHDOG. Reload brcmfmac module when blindbug is detected, instead of rebooting. Adapted from WATCHDOG.
""" """
@ -43,19 +43,20 @@ class Fix_Services(plugins.Plugin):
self._status = "--" self._status = "--"
self._count = 0 self._count = 0
def on_loaded(self): def on_loaded(self):
""" """
Gets called when the plugin gets loaded Gets called when the plugin gets loaded
""" """
self._status = "ld" self._status = "ld"
logging.info("[Fix_Services] plugin loaded.") logging.info("[FixBRCMF] plugin loaded.")
def on_ready(self, agent): def on_ready(self, agent):
last_lines = ''.join(list(TextIOWrapper(subprocess.Popen(['journalctl', '-n10', '-k'], last_lines = ''.join(list(TextIOWrapper(subprocess.Popen(['journalctl', '-n10', '-k'],
stdout=subprocess.PIPE).stdout))[-10:]) stdout=subprocess.PIPE).stdout))[-10:])
try: try:
cmd_output = subprocess.check_output("ip link show wlan0mon", shell=True) cmd_output = subprocess.check_output("ip link show wlan0mon", shell=True)
logging.info("[Fix_Services ip link show wlan0mon]: %s" % repr(cmd_output)) logging.info("[FixBRCMF ip link show wlan0mon]: %s" % repr(cmd_output))
if ",UP," in str(cmd_output): if ",UP," in str(cmd_output):
logging.info("wlan0mon is up.") logging.info("wlan0mon is up.")
self._status = "up" self._status = "up"
@ -66,35 +67,35 @@ class Fix_Services(plugins.Plugin):
display = agent.view() display = agent.view()
display.set('status', 'Blind-Bug detected. Restarting.') display.set('status', 'Blind-Bug detected. Restarting.')
display.update(force=True) display.update(force=True)
logging.info('[Fix_Services] Blind-Bug detected. Restarting.\n%s' % repr(last_lines)) logging.info('[FixBRCMF] Blind-Bug detected. Restarting.\n%s' % repr(last_lines))
try: try:
self._tryTurningItOffAndOnAgain(agent) self._tryTurningItOffAndOnAgain(agent)
except Exception as err: except Exception as err:
logging.warning("[Fix_Services turnOffAndOn] %s" % repr(err)) logging.warning("[FixBRCMF turnOffAndOn] %s" % repr(err))
else: else:
logging.info("[Fix_Services] Logs look good, too:\n%s" % last_lines) logging.info("[FixBRCMF] Logs look good, too:\n%s" % last_lines)
self._status = "" self._status = ""
except Exception as err: except Exception as err:
logging.error("[Fix_Services ip link show wlan0mon]: %s" % repr(err)) logging.error("[FixBRCMF ip link show wlan0mon]: %s" % repr(err))
try: try:
self._status = "xx" self._status = "xx"
self._tryTurningItOffAndOnAgain(agent) self._tryTurningItOffAndOnAgain(agent)
except Exception as err: except Exception as err:
logging.error("[Fix_Services OffNOn]: %s" % repr(err)) logging.error("[FixBRCMF OffNOn]: %s" % repr(err))
# bettercap sys_log event # bettercap sys_log event
# search syslog events for the brcmf channel fail, and reset when it shows up # search syslog events for the brcmf channel fail, and reset when it shows up
# apparently this only gets messages from bettercap going to syslog, not from syslog # apparently this only gets messages from bettercap going to syslog, not from syslog
def on_bcap_sys_log(self, agent, event): def on_bcap_sys_log(self, agent, event):
if re.search('wifi error while hopping to channel', event['data']['Message']): if re.search('wifi error while hopping to channel', event['data']['Message']):
logging.info("[Fix_Services]SYSLOG MATCH: %s" % event['data']['Message']) logging.info("[FixBRCMF]SYSLOG MATCH: %s" % event['data']['Message'])
logging.info("[Fix_Services]**** restarting wifi.recon") logging.info("[FixBRCMF]**** restarting wifi.recon")
try: try:
result = agent.run("wifi.recon off; wifi.recon on") result = agent.run("wifi.recon off; wifi.recon on")
if result["success"]: if result["success"]:
logging.info("[Fix_Services] wifi.recon flip: success!") logging.info("[FixBRCMF] wifi.recon flip: success!")
if hasattr(agent, 'view'): if hasattr(agent, 'view'):
display = agent.view() display = agent.view()
if display: display.update(force=True, new_data={"status": "Wifi recon flipped!", if display: display.update(force=True, new_data={"status": "Wifi recon flipped!",
@ -102,38 +103,37 @@ class Fix_Services(plugins.Plugin):
else: else:
print("Wifi recon flipped") print("Wifi recon flipped")
else: else:
logging.warning("[Fix_Services] wifi.recon flip: FAILED: %s" % repr(result)) logging.warning("[FixBRCMF] wifi.recon flip: FAILED: %s" % repr(result))
except Exception as err: except Exception as err:
logging.error("[Fix_Services]SYSLOG wifi.recon flip fail: %s" % err) logging.error("[FixBRCMF]SYSLOG wifi.recon flip fail: %s" % err)
def on_epoch(self, agent, epoch, epoch_data): def on_epoch(self, agent, epoch, epoch_data):
last_lines = ''.join(list(TextIOWrapper(subprocess.Popen(['journalctl', '-n10', '-k'], last_lines = ''.join(list(TextIOWrapper(subprocess.Popen(['journalctl', '-n10', '-k'],
stdout=subprocess.PIPE).stdout))[-10:]) stdout=subprocess.PIPE).stdout))[-10:])
other_last_lines = ''.join(list(TextIOWrapper(subprocess.Popen(['journalctl', '-n10'], other_last_lines = ''.join(list(TextIOWrapper(subprocess.Popen(['journalctl', '-n10'],
stdout=subprocess.PIPE).stdout))[-10:]) stdout=subprocess.PIPE).stdout))[-10:])
other_other_last_lines = ''.join( other_other_last_lines = ''.join(list(TextIOWrapper(subprocess.Popen(['tail', '-n10', '/var/log/pwnagotchi.log'],
list(TextIOWrapper(subprocess.Popen(['tail', '-n10', '/var/log/pwnagotchi.log'],
stdout=subprocess.PIPE).stdout))[-10:]) stdout=subprocess.PIPE).stdout))[-10:])
# don't check if we ran a reset recently # don't check if we ran a reset recently
logging.debug("[Fix_Services]**** epoch") logging.debug("[FixBRCMF]**** epoch")
if time.time() - self.LASTTRY > 180: if time.time() - self.LASTTRY > 180:
# get last 10 lines # get last 10 lines
display = None display = None
logging.debug("[Fix_Services]**** checking") logging.debug("[FixBRCMF]**** checking")
# Look for pattern 1 # Look for pattern 1
if len(self.pattern.findall(last_lines)) >= 3: if len(self.pattern.findall(last_lines)) >= 3:
logging.info("[Fix_Services]**** Should trigger a reload of the wlan0mon device:\n%s" % last_lines) logging.info("[FixBRCMF]**** Should trigger a reload of the wlan0mon device:\n%s" % last_lines)
if hasattr(agent, 'view'): if hasattr(agent, 'view'):
display = agent.view() display = agent.view()
display.set('status', 'Blind-Bug detected. Restarting.') display.set('status', 'Blind-Bug detected. Restarting.')
display.update(force=True) display.update(force=True)
logging.info('[Fix_Services] Blind-Bug detected. Restarting.') logging.info('[FixBRCMF] Blind-Bug detected. Restarting.')
try: try:
self._tryTurningItOffAndOnAgain(agent) self._tryTurningItOffAndOnAgain(agent)
except Exception as err: except Exception as err:
logging.warning("[Fix_Services] TTOAOA: %s" % repr(err)) logging.warning("[FixBRCMF] TTOAOA: %s" % repr(err))
# Look for pattern 2 # Look for pattern 2
elif len(self.pattern2.findall(other_last_lines)) >= 5: elif len(self.pattern2.findall(other_last_lines)) >= 5:
@ -141,12 +141,12 @@ class Fix_Services(plugins.Plugin):
display = agent.view() display = agent.view()
display.set('status', 'Wifi channel stuck. Restarting recon.') display.set('status', 'Wifi channel stuck. Restarting recon.')
display.update(force=True) display.update(force=True)
logging.info('[Fix_Services] Wifi channel stuck. Restarting recon.') logging.info('[FixBRCMF] Wifi channel stuck. Restarting recon.')
try: try:
result = agent.run("wifi.recon off; wifi.recon on") result = agent.run("wifi.recon off; wifi.recon on")
if result["success"]: if result["success"]:
logging.info("[Fix_Services] wifi.recon flip: success!") logging.info("[FixBRCMF] wifi.recon flip: success!")
if display: if display:
display.update(force=True, new_data={"status": "Wifi recon flipped!", display.update(force=True, new_data={"status": "Wifi recon flipped!",
"brcmfmac_status": self._status, "brcmfmac_status": self._status,
@ -154,14 +154,14 @@ class Fix_Services(plugins.Plugin):
else: else:
print("Wifi recon flipped\nthat was easy!") print("Wifi recon flipped\nthat was easy!")
else: else:
logging.warning("[Fix_Services] wifi.recon flip: FAILED: %s" % repr(result)) logging.warning("[FixBRCMF] wifi.recon flip: FAILED: %s" % repr(result))
except Exception as err: except Exception as err:
logging.error("[Fix_Services wifi.recon flip] %s" % repr(err)) logging.error("[FixBRCMF wifi.recon flip] %s" % repr(err))
# Look for pattern 3 # Look for pattern 3
elif len(self.pattern3.findall(other_other_last_lines)) >= 1: elif len(self.pattern3.findall(other_other_last_lines)) >= 1:
logging.info("[Fix_Services] Firmware has halted or crashed. Restarting wlan0mon.") logging.info("[FixBRCMF] Firmware has halted or crashed. Restarting wlan0mon.")
if hasattr(agent, 'view'): if hasattr(agent, 'view'):
display = agent.view() display = agent.view()
display.set('status', 'Firmware has halted or crashed. Restarting wlan0mon.') display.set('status', 'Firmware has halted or crashed. Restarting wlan0mon.')
@ -170,13 +170,13 @@ class Fix_Services(plugins.Plugin):
# Run the monstart command to restart wlan0mon # Run the monstart command to restart wlan0mon
cmd_output = subprocess.check_output("monstart", shell=True) cmd_output = subprocess.check_output("monstart", shell=True)
self._status = "up" self._status = "up"
logging.info("[Fix_Services monstart]: %s" % repr(cmd_output)) logging.info("[FixBRCMF monstart]: %s" % repr(cmd_output))
except Exception as err: except Exception as err:
logging.error("[Fix_Services monstart]: %s" % repr(err)) logging.error("[FixBRCMF monstart]: %s" % repr(err))
# Look for pattern 3 # Look for pattern 3
elif len(self.pattern3.findall(other_other_last_lines)) >= 1: elif len(self.pattern3.findall(other_other_last_lines)) >= 1:
logging.info("[Fix_Services] wlan0 is down!") logging.info("[FixBRCMF] wlan0 is down!")
if hasattr(agent, 'view'): if hasattr(agent, 'view'):
display = agent.view() display = agent.view()
display.set('status', 'Restarting wlan0 now!') display.set('status', 'Restarting wlan0 now!')
@ -185,9 +185,9 @@ class Fix_Services(plugins.Plugin):
# Run the monstart command to restart wlan0mon # Run the monstart command to restart wlan0mon
cmd_output = subprocess.check_output("ifconfig wlan0 up && monstart", shell=True) cmd_output = subprocess.check_output("ifconfig wlan0 up && monstart", shell=True)
self._status = "up" self._status = "up"
logging.info("[Fix_Services monstart]: %s" % repr(cmd_output)) logging.info("[FixBRCMF monstart]: %s" % repr(cmd_output))
except Exception as err: except Exception as err:
logging.error("[Fix_Services monstart]: %s" % repr(err)) logging.error("[FixBRCMF monstart]: %s" % repr(err))
else: else:
print("logs look good") print("logs look good")
@ -216,7 +216,7 @@ class Fix_Services(plugins.Plugin):
# avoid overlapping restarts, but allow it if it's been a while # avoid overlapping restarts, but allow it if it's been a while
# (in case the last attempt failed before resetting "isReloadingMon") # (in case the last attempt failed before resetting "isReloadingMon")
if self.isReloadingMon and (time.time() - self.LASTTRY) < 180: if self.isReloadingMon and (time.time() - self.LASTTRY) < 180:
logging.info("[Fix_Services] Duplicate attempt ignored") logging.info("[FixBRCMF] Duplicate attempt ignored")
else: else:
self.isReloadingMon = True self.isReloadingMon = True
self.LASTTRY = time.time() self.LASTTRY = time.time()
@ -242,7 +242,7 @@ class Fix_Services(plugins.Plugin):
# is it up? # is it up?
try: try:
cmd_output = subprocess.check_output("ip link show wlan0mon", shell=True) cmd_output = subprocess.check_output("ip link show wlan0mon", shell=True)
logging.info("[Fix_Services ip link show wlan0mon]: %s" % repr(cmd_output)) logging.info("[FixBRCMF ip link show wlan0mon]: %s" % repr(cmd_output))
if ",UP," in str(cmd_output): if ",UP," in str(cmd_output):
logging.info("wlan0mon is up. Skip reset?") logging.info("wlan0mon is up. Skip reset?")
# not reliable, so don't skip just yet # not reliable, so don't skip just yet
@ -250,33 +250,33 @@ class Fix_Services(plugins.Plugin):
# self.isReloadingMon = False # self.isReloadingMon = False
# return # return
except Exception as err: except Exception as err:
logging.error("[Fix_Services ip link show wlan0mon]: %s" % repr(err)) logging.error("[FixBRCMF ip link show wlan0mon]: %s" % repr(err))
try: try:
result = connection.run("wifi.recon off") result = connection.run("wifi.recon off")
if "success" in result: if "success" in result:
self.logPrintView("info", "[Fix_Services] wifi.recon off: %s!" % repr(result), self.logPrintView("info", "[FixBRCMF] wifi.recon off: %s!" % repr(result),
display, {"status": "Wifi recon paused!", "face": faces.COOL}) display, {"status": "Wifi recon paused!", "face": faces.COOL})
time.sleep(2) time.sleep(2)
else: else:
self.logPrintView("warning", "[Fix_Services] wifi.recon off: FAILED: %s" % repr(result), self.logPrintView("warning", "[FixBRCMF] wifi.recon off: FAILED: %s" % repr(result),
display, {"status": "Recon was busted (probably)", display, {"status": "Recon was busted (probably)",
"face": random.choice((faces.BROKEN, faces.DEBUG))}) "face": random.choice((faces.BROKEN, faces.DEBUG))})
except Exception as err: except Exception as err:
logging.error("[Fix_Services wifi.recon off] error %s" % (repr(err))) logging.error("[FixBRCMF wifi.recon off] error %s" % (repr(err)))
logging.info("[Fix_Services] recon paused. Now trying wlan0mon reload") logging.info("[FixBRCMF] recon paused. Now trying wlan0mon reload")
try: try:
cmd_output = subprocess.check_output("monstop", shell=True) cmd_output = subprocess.check_output("monstop", shell=True)
self._status = "dn" self._status = "dn"
self.logPrintView("info", "[Fix_Services] wlan0mon down and deleted: %s" % cmd_output, self.logPrintView("info", "[FixBRCMF] wlan0mon down and deleted: %s" % cmd_output,
display, {"status": "wlan0mon d-d-d-down!", "face": faces.BORED}) display, {"status": "wlan0mon d-d-d-down!", "face": faces.BORED})
except Exception as nope: except Exception as nope:
logging.error("[Fix_Services delete wlan0mon] %s" % nope) logging.error("[FixBRCMF delete wlan0mon] %s" % nope)
pass pass
logging.debug("[Fix_Services] Now trying modprobe -r") logging.debug("[FixBRCMF] Now trying modprobe -r")
# Try this sequence 3 times until it is reloaded # Try this sequence 3 times until it is reloaded
# #
@ -287,7 +287,7 @@ class Fix_Services(plugins.Plugin):
try: try:
# unload the module # unload the module
cmd_output = subprocess.check_output("sudo modprobe -r brcmfmac", shell=True) cmd_output = subprocess.check_output("sudo modprobe -r brcmfmac", shell=True)
self.logPrintView("info", "[Fix_Services] unloaded brcmfmac", display, self.logPrintView("info", "[FixBRCMF] unloaded brcmfmac", display,
{"status": "Turning it off #%d" % tries, "face": faces.SMART}) {"status": "Turning it off #%d" % tries, "face": faces.SMART})
self._status = "ul" self._status = "ul"
time.sleep(1 + tries) time.sleep(1 + tries)
@ -307,41 +307,39 @@ class Fix_Services(plugins.Plugin):
"monstart", "monstart",
shell=True) shell=True)
self.logPrintView("info", self.logPrintView("info",
"[Fix_Services interface add wlan0mon] worked #%d: %s" % ( "[FixBRCMF interface add wlan0mon] worked #%d: %s" % (tries, cmd_output))
tries, cmd_output))
self._status = "up" self._status = "up"
time.sleep(tries + 5) time.sleep(tries + 5)
try: try:
# try accessing mon0 in bettercap # try accessing mon0 in bettercap
result = connection.run("set wifi.interface wlan0mon") result = connection.run("set wifi.interface wlan0mon")
if "success" in result: if "success" in result:
logging.info("[Fix_Services set wifi.interface wlan0mon] worked: %s" % repr(result)) logging.info("[FixBRCMF set wifi.interface wlan0mon] worked: %s" % repr(result))
self._status = "" self._status = ""
self._count = self._count + 1 self._count = self._count + 1
time.sleep(1) time.sleep(1)
# stop looping and get back to recon # stop looping and get back to recon
break break
else: else:
logging.info( logging.info("[FixBRCMF set wifi.interfaceface wlan0mon] failed? %s" % repr(result))
"[Fix_Services set wifi.interfaceface wlan0mon] failed? %s" % repr(result))
except Exception as err: except Exception as err:
logging.info( logging.info(
"[Fix_Services set wifi.interface wlan0mon] except: %s" % (repr(result), repr(err))) "[FixBRCMF set wifi.interface wlan0mon] except: %s" % (repr(result), repr(err)))
except Exception as cerr: # except Exception as cerr: #
if not display: print("failed loading wlan0mon attempt #%d: %s" % (tries, repr(cerr))) if not display: print("failed loading wlan0mon attempt #%d: %s" % (tries, repr(cerr)))
except Exception as err: # from modprobe except Exception as err: # from modprobe
if not display: print("Failed reloading brcmfmac") if not display: print("Failed reloading brcmfmac")
logging.error("[Fix_Services] Failed reloading brcmfmac %s" % repr(err)) logging.error("[FixBRCMF] Failed reloading brcmfmac %s" % repr(err))
except Exception as nope: # from modprobe -r except Exception as nope: # from modprobe -r
# fails if already unloaded, so probably fine # fails if already unloaded, so probably fine
logging.error("[Fix_Services #%d modprobe -r] %s" % (tries, repr(nope))) logging.error("[FixBRCMF #%d modprobe -r] %s" % (tries, repr(nope)))
if not display: print("[Fix_Services #%d modprobe -r] %s" % (tries, repr(nope))) if not display: print("[FixBRCMF #%d modprobe -r] %s" % (tries, repr(nope)))
pass pass
tries = tries + 1 tries = tries + 1
if tries < 3: if tries < 3:
logging.info("[Fix_Services] wlan0mon didn't make it. trying again") logging.info("[FixBRCMF] wlan0mon didn't make it. trying again")
if not display: print(" wlan0mon didn't make it. trying again") if not display: print(" wlan0mon didn't make it. trying again")
# exited the loop, so hopefully it loaded # exited the loop, so hopefully it loaded
@ -352,14 +350,14 @@ class Fix_Services(plugins.Plugin):
"face": faces.INTENSE}) "face": faces.INTENSE})
else: else:
print("And back on again...") print("And back on again...")
logging.info("[Fix_Services] wlan0mon back up") logging.info("[FixBRCMF] wlan0mon back up")
else: else:
self.LASTTRY = time.time() self.LASTTRY = time.time()
time.sleep(8 + tries * 2) # give it a bit before restarting recon in bettercap time.sleep(8 + tries * 2) # give it a bit before restarting recon in bettercap
self.isReloadingMon = False self.isReloadingMon = False
logging.info("[Fix_Services] re-enable recon") logging.info("[FixBRCMF] re-enable recon")
try: try:
result = connection.run("wifi.clear; wifi.recon on") result = connection.run("wifi.clear; wifi.recon on")
@ -371,15 +369,15 @@ class Fix_Services(plugins.Plugin):
"face": faces.HAPPY}) "face": faces.HAPPY})
else: else:
print("I can see again") print("I can see again")
logging.info("[Fix_Services] wifi.recon on %s" % repr(result)) logging.info("[FixBRCMF] wifi.recon on %s" % repr(result))
self.LASTTRY = time.time() + 120 # 2-minute pause until next time. self.LASTTRY = time.time() + 120 # 2-minute pause until next time.
else: else:
logging.error("[Fix_Services] wifi.recon did not start up: %s" % repr(result)) logging.error("[FixBRCMF] wifi.recon did not start up: %s" % repr(result))
self.LASTTRY = time.time() - 300 # failed, so try again ASAP self.LASTTRY = time.time() - 300 # failed, so try again ASAP
self.isReloadingMon = False self.isReloadingMon = False
except Exception as err: except Exception as err:
logging.error("[Fix_Services wifi.recon on] %s" % repr(err)) logging.error("[FixBRCMF wifi.recon on] %s" % repr(err))
# called to setup the ui elements # called to setup the ui elements
def on_ui_setup(self, ui): def on_ui_setup(self, ui):
@ -405,16 +403,15 @@ class Fix_Services(plugins.Plugin):
def on_unload(self, ui): def on_unload(self, ui):
try: try:
ui.remove_element('brcmfmac_status') ui.remove_element('brcmfmac_status')
logging.info("[Fix_Services] unloaded") logging.info("[FixBRCMF] unloaded")
except Exception as err: except Exception as err:
logging.info("[Fix_Services] unload err %s " % repr(err)) logging.info("[FixBRCMF] unload err %s " % repr(err))
pass pass
# run from command line to brute force a reload # run from command line to brute force a reload
if __name__ == "__main__": if __name__ == "__main__":
print("Performing brcmfmac reload and restart wlan0mon in 5 seconds...") print("Performing brcmfmac reload and restart wlan0mon in 5 seconds...")
fb = Fix_Services() fb = Fix_BRCMF()
data = {'Message': "kernel: brcmfmac: brcmf_cfg80211_nexmon_set_channel: Set Channel failed: chspec=1234"} data = {'Message': "kernel: brcmfmac: brcmf_cfg80211_nexmon_set_channel: Set Channel failed: chspec=1234"}
event = {'data': data} event = {'data': data}
@ -424,5 +421,5 @@ if __name__ == "__main__":
time.sleep(2) time.sleep(2)
print("3 seconds") print("3 seconds")
time.sleep(3) time.sleep(3)
fb.on_epoch(agent, event, None) # fb.on_epoch(agent, event, None)
# fb._tryTurningItOffAndOnAgain(agent) fb._tryTurningItOffAndOnAgain(agent)