diff --git a/scapy/layers/bluetooth.py b/scapy/layers/bluetooth.py index 662bad3b37b7cbdde0fb3d46f915df7cf81bde81..590e70ebb6216def3864f4b993e9b092d85ea375 100644 --- a/scapy/layers/bluetooth.py +++ b/scapy/layers/bluetooth.py @@ -1,19 +1,51 @@ ## This file is part of Scapy ## See http://www.secdev.org/projects/scapy for more informations ## Copyright (C) Philippe Biondi <phil@secdev.org> +## Copyright (C) Mike Ryan <mikeryan@lacklustre.net> ## This program is published under a GPLv2 license """ Bluetooth layers, sockets and send/receive functions. """ -import socket,struct +import socket,struct,array +from ctypes import * from scapy.config import conf from scapy.packet import * from scapy.fields import * from scapy.supersocket import SuperSocket from scapy.data import MTU +from select import select + +########## +# Fields # +########## + +class XLEShortField(LEShortField): + def i2repr(self, pkt, x): + return lhex(self.i2h(pkt, x)) + +class LEMACField(Field): + def __init__(self, name, default): + Field.__init__(self, name, default, "6s") + def i2m(self, pkt, x): + if x is None: + return "\0\0\0\0\0\0" + return mac2str(x)[::-1] + def m2i(self, pkt, x): + return str2mac(x[::-1]) + def any2i(self, pkt, x): + if type(x) is str and len(x) is 6: + x = self.m2i(pkt, x) + return x + def i2repr(self, pkt, x): + x = self.i2h(pkt, x) + if self in conf.resolve: + x = conf.manufdb._resolve_MAC(x) + return x + def randval(self): + return RandMAC() class HCI_Hdr(Packet): @@ -34,21 +66,21 @@ class HCI_ACL_Hdr(Packet): l = len(p)-4 p = p[:2]+chr(l&0xff)+chr((l>>8)&0xff)+p[4:] return p - + class L2CAP_Hdr(Packet): name = "L2CAP header" fields_desc = [ LEShortField("len",None), - LEShortEnumField("cid",0,{1:"control"}),] - + LEShortEnumField("cid",0,{1:"control", 4:"attribute"}),] + def post_build(self, p, pay): p += pay if self.len is None: - l = len(p)-4 - p = p[:2]+chr(l&0xff)+chr((l>>8)&0xff)+p[4:] + l = len(pay) + p = chr(l&0xff)+chr((l>>8)&0xff)+p[2:] return p - - + + class L2CAP_CmdHdr(Packet): name = "L2CAP command header" @@ -96,7 +128,7 @@ class L2CAP_CmdRej(Packet): name = "L2CAP Command Rej" fields_desc = [ LEShortField("reason",0), ] - + class L2CAP_ConfReq(Packet): name = "L2CAP Conf Req" @@ -126,7 +158,7 @@ class L2CAP_DisconnResp(Packet): def answers(self, other): return self.scid == other.scid - + class L2CAP_InfoReq(Packet): name = "L2CAP Info Req" @@ -144,9 +176,351 @@ class L2CAP_InfoResp(Packet): return self.type == other.type +class ATT_Hdr(Packet): + name = "ATT header" + fields_desc = [ XByteField("opcode", None), ] + + +class ATT_Error_Response(Packet): + name = "Error Response" + fields_desc = [ XByteField("request", 0), + LEShortField("handle", 0), + XByteField("ecode", 0), ] + +class ATT_Exchange_MTU_Request(Packet): + name = "Exchange MTU Request" + fields_desc = [ LEShortField("mtu", 0), ] + +class ATT_Exchange_MTU_Response(Packet): + name = "Exchange MTU Response" + fields_desc = [ LEShortField("mtu", 0), ] + +class ATT_Find_Information_Request(Packet): + name = "Find Information Request" + fields_desc = [ XLEShortField("start", 0x0000), + XLEShortField("end", 0xffff), ] + +class ATT_Find_Information_Response(Packet): + name = "Find Information Reponse" + fields_desc = [ XByteField("format", 1), + StrField("data", "") ] + +class ATT_Find_By_Type_Value_Request(Packet): + name = "Find By Type Value Request" + fields_desc = [ XLEShortField("start", 0x0001), + XLEShortField("end", 0xffff), + XLEShortField("uuid", None), + StrField("data", ""), ] + +class ATT_Find_By_Type_Value_Response(Packet): + name = "Find By Type Value Response" + fields_desc = [ StrField("handles", ""), ] + +class ATT_Read_By_Type_Request(Packet): + name = "Read By Type Request" + fields_desc = [ XLEShortField("start", 0x0001), + XLEShortField("end", 0xffff), + XLEShortField("uuid", None), ] + +class ATT_Read_By_Type_Response(Packet): + name = "Read By Type Response" + # fields_desc = [ FieldLenField("len", None, length_of="data", fmt="B"), + # StrLenField("data", "", length_from=lambda pkt:pkt.len), ] + fields_desc = [ StrField("data", "") ] + +class ATT_Read_Request(Packet): + name = "Read Request" + fields_desc = [ XLEShortField("gatt_handle", 0), ] + +class ATT_Read_Response(Packet): + name = "Read Response" + fields_desc = [ StrField("value", ""), ] + +class ATT_Read_By_Group_Type_Request(Packet): + name = "Read By Group Type Request" + fields_desc = [ XLEShortField("start", 0), + XLEShortField("end", 0xffff), + XLEShortField("uuid", 0), ] + +class ATT_Read_By_Group_Type_Response(Packet): + name = "Read By Group Type Response" + fields_desc = [ XByteField("length", 0), + StrField("data", ""), ] + +class ATT_Write_Request(Packet): + name = "Write Request" + fields_desc = [ XLEShortField("gatt_handle", 0), + StrField("data", ""), ] + +class ATT_Write_Command(Packet): + name = "Write Request" + fields_desc = [ XLEShortField("gatt_handle", 0), + StrField("data", ""), ] + +class ATT_Write_Response(Packet): + name = "Write Response" + fields_desc = [ ] + +class ATT_Handle_Value_Notification(Packet): + name = "Handle Value Notification" + fields_desc = [ XLEShortField("handle", 0), + StrField("value", ""), ] + + +class SM_Hdr(Packet): + name = "SM header" + fields_desc = [ ByteField("sm_command", None) ] + + +class SM_Pairing_Request(Packet): + name = "Pairing Request" + fields_desc = [ ByteEnumField("iocap", 3, {0:"DisplayOnly", 1:"DisplayYesNo", 2:"KeyboardOnly", 3:"NoInputNoOutput", 4:"KeyboardDisplay"}), + ByteEnumField("oob", 0, {0:"Not Present", 1:"Present (from remote device)"}), + BitField("authentication", 0, 8), + ByteField("max_key_size", 16), + ByteField("initiator_key_distribution", 0), + ByteField("responder_key_distribution", 0), ] + +class SM_Pairing_Response(Packet): + name = "Pairing Response" + fields_desc = [ ByteEnumField("iocap", 3, {0:"DisplayOnly", 1:"DisplayYesNo", 2:"KeyboardOnly", 3:"NoInputNoOutput", 4:"KeyboardDisplay"}), + ByteEnumField("oob", 0, {0:"Not Present", 1:"Present (from remote device)"}), + BitField("authentication", 0, 8), + ByteField("max_key_size", 16), + ByteField("initiator_key_distribution", 0), + ByteField("responder_key_distribution", 0), ] + + +class SM_Confirm(Packet): + name = "Pairing Confirm" + fields_desc = [ StrFixedLenField("confirm", '\x00' * 16, 16) ] + +class SM_Random(Packet): + name = "Pairing Random" + fields_desc = [ StrFixedLenField("random", '\x00' * 16, 16) ] + +class SM_Failed(Packet): + name = "Pairing Failed" + fields_desc = [ XByteField("reason", 0) ] + +class SM_Encryption_Information(Packet): + name = "Encryption Information" + fields_desc = [ StrFixedLenField("ltk", "\x00" * 16, 16), ] + +class SM_Master_Identification(Packet): + name = "Master Identification" + fields_desc = [ XLEShortField("ediv", 0), + StrFixedLenField("rand", '\x00' * 8, 8), ] + +class HCI_Command_Hdr(Packet): + name = "HCI Command header" + fields_desc = [ XLEShortField("opcode", 0), + ByteField("len", None), ] + + def post_build(self, p, pay): + p += pay + if self.len is None: + l = len(p)-3 + p = p[:2]+chr(l&0xff)+p[3:] + return p + +class HCI_Cmd_Reset(Packet): + name = "Reset" + +class HCI_Cmd_Set_Event_Filter(Packet): + name = "Set Event Filter" + fields_desc = [ ByteEnumField("type", 0, {0:"clear"}), ] + +class HCI_Cmd_Connect_Accept_Timeout(Packet): + name = "Connection Attempt Timeout" + fields_desc = [ LEShortField("timeout", 32000) ] # 32000 slots is 20000 msec + +class HCI_Cmd_LE_Host_Supported(Packet): + name = "LE Host Supported" + fields_desc = [ ByteField("supported", 1), + ByteField("simultaneous", 1), ] + +class HCI_Cmd_Set_Event_Mask(Packet): + name = "Set Event Mask" + fields_desc = [ StrFixedLenField("mask", "\xff\xff\xfb\xff\x07\xf8\xbf\x3d", 8) ] + +class HCI_Cmd_Read_BD_Addr(Packet): + name = "Read BD Addr" + + +class HCI_Cmd_LE_Set_Scan_Parameters(Packet): + name = "LE Set Scan Parameters" + fields_desc = [ ByteEnumField("type", 1, {1:"active"}), + XLEShortField("interval", 16), + XLEShortField("window", 16), + ByteEnumField("atype", 0, {0:"public"}), + ByteEnumField("policy", 0, {0:"all"}), ] + +class HCI_Cmd_LE_Set_Scan_Enable(Packet): + name = "LE Set Scan Enable" + fields_desc = [ ByteField("enable", 1), + ByteField("filter_dups", 1), ] -bind_layers( HCI_Hdr, HCI_ACL_Hdr, type=2) +class HCI_Cmd_LE_Create_Connection(Packet): + name = "LE Create Connection" + fields_desc = [ LEShortField("interval", 96), + LEShortField("window", 48), + ByteEnumField("filter", 0, {0:"address"}), + ByteEnumField("patype", 0, {0:"public", 1:"random"}), + LEMACField("paddr", None), + ByteEnumField("atype", 0, {0:"public", 1:"random"}), + LEShortField("min_interval", 40), + LEShortField("max_interval", 56), + LEShortField("latency", 0), + LEShortField("timeout", 42), + LEShortField("min_ce", 0), + LEShortField("max_ce", 0), ] + +class HCI_Cmd_LE_Read_Buffer_Size(Packet): + name = "LE Read Buffer Size" + +class HCI_Cmd_LE_Set_Random_Address(Packet): + name = "LE Set Random Address" + fields_desc = [ LEMACField("address", None) ] + +class HCI_Cmd_LE_Set_Advertising_Parameters(Packet): + name = "LE Set Advertising Parameters" + fields_desc = [ LEShortField("interval_min", 0x0800), + LEShortField("interval_max", 0x0800), + ByteEnumField("adv_type", 0, {0:"ADV_IND", 1:"ADV_DIRECT_IND", 2:"ADV_SCAN_IND", 3:"ADV_NONCONN_IND", 4:"ADV_DIRECT_IND_LOW"}), + ByteEnumField("oatype", 0, {0:"public", 1:"random"}), + ByteEnumField("datype", 0, {0:"public", 1:"random"}), + LEMACField("daddr", None), + ByteField("channel_map", 7), + ByteEnumField("filter_policy", 0, {0:"all:all", 1:"connect:all scan:whitelist", 2:"connect:whitelist scan:all", 3:"all:whitelist"}), ] + +class HCI_Cmd_LE_Set_Advertising_Data(Packet): + name = "LE Set Advertising Data" + fields_desc = [ FieldLenField("len", None, length_of="data", fmt="B"), + StrLenField("data", "", length_from=lambda pkt:pkt.len), ] + +class HCI_Cmd_LE_Set_Advertise_Enable(Packet): + name = "LE Set Advertise Enable" + fields_desc = [ ByteField("enable", 0) ] + +class HCI_Cmd_LE_Long_Term_Key_Request_Negative_Reply(Packet): + name = "LE Long Term Key Request Negative Reply" + fields_desc = [ LEShortField("handle", 0), ] + +class HCI_Cmd_LE_Long_Term_Key_Request_Reply(Packet): + name = "LE Long Term Key Request Reply" + fields_desc = [ LEShortField("handle", 0), + StrFixedLenField("ltk", '\x00' * 16, 16), ] + +class HCI_Event_Hdr(Packet): + name = "HCI Event header" + fields_desc = [ XByteField("code", 0), + ByteField("length", 0), ] + + +class HCI_Event_Disconnection_Complete(Packet): + name = "Disconnection Complete" + fields_desc = [ ByteEnumField("status", 0, {0:"success"}), + LEShortField("handle", 0), + XByteField("reason", 0), ] + + +class HCI_Event_Encryption_Change(Packet): + name = "Encryption Change" + fields_desc = [ ByteEnumField("status", 0, {0:"change has occurred"}), + LEShortField("handle", 0), + ByteEnumField("enabled", 0, {0:"OFF", 1:"ON (LE)", 2:"ON (BR/EDR)"}), ] + +class HCI_Event_Command_Complete(Packet): + name = "Command Complete" + fields_desc = [ ByteField("number", 0), + XLEShortField("opcode", 0), + ByteEnumField("status", 0, {0:"success"}), ] + + +class HCI_Cmd_Complete_Read_BD_Addr(Packet): + name = "Read BD Addr" + fields_desc = [ LEMACField("addr", None), ] + + + +class HCI_Event_Command_Status(Packet): + name = "Command Status" + fields_desc = [ ByteEnumField("status", 0, {0:"pending"}), + ByteField("number", 0), + XLEShortField("opcode", None), ] + +class HCI_Event_Number_Of_Completed_Packets(Packet): + name = "Number Of Completed Packets" + fields_desc = [ ByteField("number", 0) ] + +class HCI_Event_LE_Meta(Packet): + name = "LE Meta" + fields_desc = [ ByteEnumField("event", 0, {2:"advertising_report"}) ] + +class HCI_LE_Meta_Connection_Complete(Packet): + name = "Connection Complete" + fields_desc = [ ByteEnumField("status", 0, {0:"success"}), + LEShortField("handle", 0), + ByteEnumField("role", 0, {0:"master"}), + ByteEnumField("patype", 0, {0:"public", 1:"random"}), + LEMACField("paddr", None), + LEShortField("interval", 54), + LEShortField("latency", 0), + LEShortField("supervision", 42), + XByteField("clock_latency", 5), ] + +class HCI_LE_Meta_Advertising_Report(Packet): + name = "Advertising Report" + fields_desc = [ ByteField("number", 0), + ByteEnumField("type", 0, {0:"conn_und", 4:"scan_rsp"}), + ByteEnumField("atype", 0, {0:"public", 1:"random"}), + LEMACField("addr", None), + FieldLenField("len", None, length_of="data", fmt="B"), + StrLenField("data", "", length_from=lambda pkt:pkt.len), ] + +class HCI_LE_Meta_Long_Term_Key_Request(Packet): + name = "Long Term Key Request" + fields_desc = [ LEShortField("handle", 0), + StrFixedLenField("rand", None, 8), + XLEShortField("ediv", 0), ] + + +bind_layers( HCI_Hdr, HCI_Command_Hdr, type=1) +bind_layers( HCI_Hdr, HCI_ACL_Hdr, type=2) +bind_layers( HCI_Hdr, HCI_Event_Hdr, type=4) bind_layers( HCI_Hdr, conf.raw_layer, ) + +bind_layers( HCI_Command_Hdr, HCI_Cmd_Reset, opcode=0x0c03) +bind_layers( HCI_Command_Hdr, HCI_Cmd_Set_Event_Mask, opcode=0x0c01) +bind_layers( HCI_Command_Hdr, HCI_Cmd_Set_Event_Filter, opcode=0x0c05) +bind_layers( HCI_Command_Hdr, HCI_Cmd_Connect_Accept_Timeout, opcode=0x0c16) +bind_layers( HCI_Command_Hdr, HCI_Cmd_LE_Host_Supported, opcode=0x0c6d) +bind_layers( HCI_Command_Hdr, HCI_Cmd_Read_BD_Addr, opcode=0x1009) +bind_layers( HCI_Command_Hdr, HCI_Cmd_LE_Read_Buffer_Size, opcode=0x2002) +bind_layers( HCI_Command_Hdr, HCI_Cmd_LE_Set_Random_Address, opcode=0x2005) +bind_layers( HCI_Command_Hdr, HCI_Cmd_LE_Set_Advertising_Parameters, opcode=0x2006) +bind_layers( HCI_Command_Hdr, HCI_Cmd_LE_Set_Advertising_Data, opcode=0x2008) +bind_layers( HCI_Command_Hdr, HCI_Cmd_LE_Set_Advertise_Enable, opcode=0x200a) +bind_layers( HCI_Command_Hdr, HCI_Cmd_LE_Set_Scan_Parameters, opcode=0x200b) +bind_layers( HCI_Command_Hdr, HCI_Cmd_LE_Set_Scan_Enable, opcode=0x200c) +bind_layers( HCI_Command_Hdr, HCI_Cmd_LE_Create_Connection, opcode=0x200d) +bind_layers( HCI_Command_Hdr, HCI_Cmd_LE_Long_Term_Key_Request_Reply, opcode=0x201a) +bind_layers( HCI_Command_Hdr, HCI_Cmd_LE_Long_Term_Key_Request_Negative_Reply, opcode=0x201b) + +bind_layers( HCI_Event_Hdr, HCI_Event_Disconnection_Complete, code=0x5) +bind_layers( HCI_Event_Hdr, HCI_Event_Encryption_Change, code=0x8) +bind_layers( HCI_Event_Hdr, HCI_Event_Command_Complete, code=0xe) +bind_layers( HCI_Event_Hdr, HCI_Event_Command_Status, code=0xf) +bind_layers( HCI_Event_Hdr, HCI_Event_Number_Of_Completed_Packets, code=0x13) +bind_layers( HCI_Event_Hdr, HCI_Event_LE_Meta, code=0x3e) + +bind_layers( HCI_Event_Command_Complete, HCI_Cmd_Complete_Read_BD_Addr, opcode=0x1009) + +bind_layers( HCI_Event_LE_Meta, HCI_LE_Meta_Connection_Complete, event=1) +bind_layers( HCI_Event_LE_Meta, HCI_LE_Meta_Advertising_Report, event=2) +bind_layers( HCI_Event_LE_Meta, HCI_LE_Meta_Long_Term_Key_Request, event=5) + bind_layers( HCI_ACL_Hdr, L2CAP_Hdr, ) bind_layers( L2CAP_Hdr, L2CAP_CmdHdr, cid=1) bind_layers( L2CAP_CmdHdr, L2CAP_CmdRej, code=1) @@ -158,19 +532,45 @@ bind_layers( L2CAP_CmdHdr, L2CAP_DisconnReq, code=6) bind_layers( L2CAP_CmdHdr, L2CAP_DisconnResp, code=7) bind_layers( L2CAP_CmdHdr, L2CAP_InfoReq, code=10) bind_layers( L2CAP_CmdHdr, L2CAP_InfoResp, code=11) - +bind_layers( L2CAP_Hdr, ATT_Hdr, cid=4) +bind_layers( ATT_Hdr, ATT_Error_Response, opcode=0x1) +bind_layers( ATT_Hdr, ATT_Exchange_MTU_Request, opcode=0x2) +bind_layers( ATT_Hdr, ATT_Exchange_MTU_Response, opcode=0x3) +bind_layers( ATT_Hdr, ATT_Find_Information_Request, opcode=0x4) +bind_layers( ATT_Hdr, ATT_Find_Information_Response, opcode=0x5) +bind_layers( ATT_Hdr, ATT_Find_By_Type_Value_Request, opcode=0x6) +bind_layers( ATT_Hdr, ATT_Find_By_Type_Value_Response, opcode=0x7) +bind_layers( ATT_Hdr, ATT_Read_By_Type_Request, opcode=0x8) +bind_layers( ATT_Hdr, ATT_Read_By_Type_Response, opcode=0x9) +bind_layers( ATT_Hdr, ATT_Read_Request, opcode=0xa) +bind_layers( ATT_Hdr, ATT_Read_Response, opcode=0xb) +bind_layers( ATT_Hdr, ATT_Read_By_Group_Type_Request, opcode=0x10) +bind_layers( ATT_Hdr, ATT_Read_By_Group_Type_Response, opcode=0x11) +bind_layers( ATT_Hdr, ATT_Write_Request, opcode=0x12) +bind_layers( ATT_Hdr, ATT_Write_Response, opcode=0x13) +bind_layers( ATT_Hdr, ATT_Write_Command, opcode=0x52) +bind_layers( ATT_Hdr, ATT_Handle_Value_Notification, opcode=0x1b) +bind_layers( L2CAP_Hdr, SM_Hdr, cid=6) +bind_layers( SM_Hdr, SM_Pairing_Request, sm_command=1) +bind_layers( SM_Hdr, SM_Pairing_Response, sm_command=2) +bind_layers( SM_Hdr, SM_Confirm, sm_command=3) +bind_layers( SM_Hdr, SM_Random, sm_command=4) +bind_layers( SM_Hdr, SM_Failed, sm_command=5) +bind_layers( SM_Hdr, SM_Encryption_Information, sm_command=6) +bind_layers( SM_Hdr, SM_Master_Identification, sm_command=7) + class BluetoothL2CAPSocket(SuperSocket): desc = "read/write packets on a connected L2CAP socket" def __init__(self, peer): s = socket.socket(socket.AF_BLUETOOTH, socket.SOCK_RAW, socket.BTPROTO_L2CAP) s.connect((peer,0)) - + self.ins = self.outs = s def recv(self, x=MTU): return L2CAP_CmdHdr(self.ins.recv(x)) - + class BluetoothHCISocket(SuperSocket): desc = "read/write on a BlueTooth HCI socket" @@ -182,11 +582,85 @@ class BluetoothHCISocket(SuperSocket): s.bind((iface,)) self.ins = self.outs = s # s.connect((peer,0)) - + def recv(self, x): return HCI_Hdr(self.ins.recv(x)) - + +class sockaddr_hci(Structure): + _fields_ = [ + ("sin_family", c_ushort), + ("hci_dev", c_ushort), + ("hci_channel", c_ushort), + ] + +class BluetoothSocketError(BaseException): + pass + +class BluetoothCommandError(BaseException): + pass + +class BluetoothUserSocket(SuperSocket): + desc = "read/write H4 over a Bluetooth user channel" + def __init__(self, adapter=0): + # s = socket.socket(socket.AF_BLUETOOTH, socket.SOCK_RAW, socket.BTPROTO_HCI) + # s.bind((0,1)) + + # yeah, if only + # thanks to Python's weak ass socket and bind implementations, we have + # to call down into libc with ctypes + + sockaddr_hcip = POINTER(sockaddr_hci) + cdll.LoadLibrary("libc.so.6") + libc = CDLL("libc.so.6") + + socket_c = libc.socket + socket_c.argtypes = (c_int, c_int, c_int); + socket_c.restype = c_int + + bind = libc.bind + bind.argtypes = (c_int, POINTER(sockaddr_hci), c_int) + bind.restype = c_int + + ######## + ## actual code + + s = socket_c(31, 3, 1) # (AF_BLUETOOTH, SOCK_RAW, HCI_CHANNEL_USER) + if s < 0: + raise BluetoothSocketError("Unable to open PF_BLUETOOTH socket") + + sa = sockaddr_hci() + sa.sin_family = 31 # AF_BLUETOOTH + sa.hci_dev = adapter # adapter index + sa.hci_channel = 1 # HCI_USER_CHANNEL + + r = bind(s, sockaddr_hcip(sa), sizeof(sa)) + if r != 0: + raise BluetoothSocketError("Unable to bind") + + self.ins = self.outs = socket.fromfd(s, 31, 3, 1) + + def send_command(self, cmd): + opcode = cmd.opcode + self.send(cmd) + while True: + r = self.recv() + if r.code == 0xe and r.opcode == opcode: + if r.status != 0: + raise BluetoothCommandError("Command %x failed with %x" % (opcode, r.status)) + return r + + def recv(self, x=512): + return HCI_Hdr(self.ins.recv(x)) + + def readable(self, timeout=0): + (ins, outs, foo) = select([self.ins], [], [], timeout) + return len(ins) > 0 + + def flush(self): + while self.readable(): + self.recv() + ## Bluetooth @@ -204,7 +678,7 @@ def srbt1(peer, pkts, *args, **kargs): a,b = srbt(peer, pkts, *args, **kargs) if len(a) > 0: return a[0][1] - - + + conf.BTsocket = BluetoothL2CAPSocket